﻿using Microsoft.VisualStudio.TextTemplating;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

namespace NetCore.Code.BLL
{
    public class TemplateHost : ITextTemplatingEngineHost
    {
        public readonly Dictionary<string, string> Locations = new Dictionary<string, string>();
        public readonly Dictionary<string, string> Contents = new Dictionary<string, string>();
        public readonly Dictionary<string, object> HostOptions = new Dictionary<string, object>();
        List<string> standardAssemblyReferences = new List<string>();
        List<string> standardImports = new List<string>();
        public readonly CompilerErrorCollection Errors = new CompilerErrorCollection();
        public readonly Dictionary<string, Type> DirectiveProcessors = new Dictionary<string, Type>();
        readonly Dictionary<ParameterKey, string> parameters = new Dictionary<ParameterKey, string>();
        string inputFile, outputFile;
        Encoding encoding;
        public virtual object GetHostOption(string optionName)
        {
            object o;
            HostOptions.TryGetValue(optionName, out o);
            return o;
        }

        public virtual bool LoadIncludeText(string requestFileName, out string content, out string location)
        {
            content = null;
            return Locations.TryGetValue(requestFileName, out location)
                && Contents.TryGetValue(requestFileName, out content);
        }

        public virtual void LogErrors(CompilerErrorCollection errors)
        {
            Errors.AddRange(errors);
        }

        public virtual AppDomain ProvideTemplatingAppDomain(string content)
        {
            return null;
        }

        public virtual string ResolveAssemblyReference(string assemblyReference)
        {
            if (System.IO.Path.IsPathRooted(assemblyReference))
                return assemblyReference;
            foreach (string referencePath in StandardAssemblyReferences)
            {
                var path = System.IO.Path.Combine(referencePath, assemblyReference);
                if (System.IO.File.Exists(path))
                    return path;
            }

            var assemblyName = new AssemblyName(assemblyReference);
            if (assemblyName.Version != null)//Load via GAC and return full path
                return Assembly.Load(assemblyName).Location;

            if (!assemblyReference.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && !assemblyReference.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                return assemblyReference + ".dll";
            return assemblyReference;
        }

        public virtual Type ResolveDirectiveProcessor(string processorName)
        {
            Type t;
            DirectiveProcessors.TryGetValue(processorName, out t);
            return t;
        }

        public virtual string ResolveParameterValue(string directiveId, string processorName, string parameterName)
        {
            return ResolveParameterValue(directiveId, processorName, parameterName);
        }

        public virtual string ResolvePath(string path)
        {
            path = ExpandParameters(path, parameters);
            path = Environment.ExpandEnvironmentVariables(path);
            if (Path.IsPathRooted(path))
                return path;
            var dir = Path.GetDirectoryName(inputFile);
            var test = Path.Combine(dir, path);
            if (File.Exists(test) || Directory.Exists(test))
                return test;
            return path;
        }

        public virtual void SetFileExtension(string extension)
        {
            extension = extension.TrimStart('.');
            if (Path.HasExtension(outputFile))
            {
                outputFile = Path.ChangeExtension(outputFile, extension);
            }
            else
            {
                outputFile = outputFile + "." + extension;
            }
        }

        public virtual void SetOutputEncoding(System.Text.Encoding encoding, bool fromOutputDirective)
        {
            this.encoding = encoding;
        }

        public virtual IList<string> StandardAssemblyReferences
        {
            get { return standardAssemblyReferences; }
        }

        public virtual IList<string> StandardImports
        {
            get { return standardImports; }
        }

        public virtual string TemplateFile
        {
            get; set;
        }
        internal static string ExpandParameters(string value, Dictionary<ParameterKey, string> parameters)
        {
            const char TokenStart = '$';
            const char TokenOpen = '(';
            const char TokenEnd = ')';

            var sb = new StringBuilder();
            for (int i = 0; i < value.Length; ++i)
            {
                if (i < value.Length - 1
                    && value[i] == TokenStart
                    && value[i + 1] == TokenOpen)
                {
                    var endTokenIndex = i;
                    while (endTokenIndex < value.Length
                        && value[endTokenIndex] != TokenEnd)
                        ++endTokenIndex;

                    if (endTokenIndex >= value.Length
                        || value[endTokenIndex] != TokenEnd)
                    {
                        // We reached the end of the string
                        // Probably not a token, or not closed token
                        sb.Append(value.Substring(i));
                        break;
                    }

                    var parameterName = value.Substring(i + 2, endTokenIndex - i - 2);
                    var key = new ParameterKey(string.Empty, string.Empty, parameterName);
                    if (parameters.TryGetValue(key, out string parameterValue))
                    {
                        sb.Append(parameterValue);
                    }
                    else
                        sb.Append(value.Substring(i, endTokenIndex - i + 1));
                    i = endTokenIndex;
                }
                else
                    sb.Append(value[i]);
            }
            return sb.ToString();
        }

        internal struct ParameterKey : IEquatable<ParameterKey>
        {
            public ParameterKey(string processorName, string directiveName, string parameterName)
            {
                this.processorName = processorName ?? "";
                this.directiveName = directiveName ?? "";
                this.parameterName = parameterName ?? "";
                unchecked
                {
                    hashCode = this.processorName.GetHashCode()
                        ^ this.directiveName.GetHashCode()
                        ^ this.parameterName.GetHashCode();
                }
            }

            string processorName, directiveName, parameterName;
            readonly int hashCode;

            public override bool Equals(object obj)
            {
                return obj is ParameterKey && Equals((ParameterKey)obj);
            }

            public bool Equals(ParameterKey other)
            {
                return processorName == other.processorName && directiveName == other.directiveName && parameterName == other.parameterName;
            }

            public override int GetHashCode()
            {
                return hashCode;
            }
        }
    }
}
